package org.zebra.web.config;

import cn.hutool.core.net.NetUtil;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import org.zebra.common.exception.StatusCode;
import springfox.documentation.builders.*;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * swagger配置
 *
 * @author zhanghongbin
 */
@Configuration
@ConditionalOnProperty(name = "zebra.web.swagger.enabled", havingValue = "true", matchIfMissing = true)
@EnableSwagger2WebMvc
@Slf4j
public class SwaggerConfig implements WebMvcConfigurer {

    public SwaggerConfig(Environment environment) {
        String port = environment.getProperty("server.port", "8080");
        String contextPath = environment.getProperty("server.servlet.context-path", "");
        log.info("knife4j web console http://" + NetUtil.getLocalhostStr() + ":" + port + contextPath + "/doc.html");
    }

    /**
     * 是否启用
     */
    private boolean enabled;

    public boolean getEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    //    /**
    //     * 通过获取版本号注解的值把带有{version}的url进行替换
    //     * <p>
    //     * 不能使用默认的 DefaultRequestHandlerCombiner 因为会把相同地址进行合并
    //     *
    //     * @return
    //     */
    //    @Bean
    //    @ConditionalOnMissingBean
    //    public RequestHandlerCombiner apiVersionRequestHandlerCombiner() {
    //        return list -> {
    //            for (RequestHandler requestHandler : list) {
    //                HandlerMethod method = requestHandler.getHandlerMethod();
    //                if (method.getBeanType().isAnnotationPresent(Api.class)) {
    //                    ApiVersion apiVersion = method.getMethod().getAnnotation(ApiVersion.class);
    //                    if (apiVersion != null) {
    //                        rewritePath(requestHandler.getPatternsCondition(), "v" + apiVersion.value());
    //                    } else {
    //                        apiVersion = method.getBeanType().getAnnotation(ApiVersion.class);
    //                        if (apiVersion != null) {
    //                            rewritePath(requestHandler.getPatternsCondition(), "v" + apiVersion.value());
    //                        }
    //                    }
    //                }
    //            }
    //            return list;
    //        };
    //    }

    //    private void rewritePath(PatternsRequestCondition requestCondition, String version) {
    //        Set<String> pathSet = requestCondition.getPatterns();
    //        List<String> tempPathList = new ArrayList<>();
    //        tempPathList.addAll(pathSet);
    //        pathSet.clear();
    //        for (String path : tempPathList) {
    //            pathSet.add(path.replaceAll("\\{version\\}", version));
    //        }
    //    }
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(value = "knife4j.enable", havingValue = "false")
    public Docket docket(
            @Autowired Environment environment, @Autowired(required = false) List<Parameter> parameterList) {
        List<ResponseMessage> responseMessages = new ArrayList<>();
        responseMessages.add(new ResponseMessageBuilder()
                .code(StatusCode.UNKNOWN.getCode())
                .message(StatusCode.UNKNOWN.getMsg())
                .build());
        responseMessages.add(new ResponseMessageBuilder()
                .code(StatusCode.PARAMS_CHECK.getCode())
                .message(StatusCode.PARAMS_CHECK.getMsg())
                .build());
        responseMessages.add(new ResponseMessageBuilder()
                .code(StatusCode.SUCCESS.getCode())
                .message(StatusCode.SUCCESS.getMsg())
                .build());
        responseMessages.add(new ResponseMessageBuilder()
                .code(StatusCode.API_VERSION_DEPRECATED.getCode())
                .message(StatusCode.API_VERSION_DEPRECATED.getMsg())
                .build());
        responseMessages.add(new ResponseMessageBuilder()
                .code(StatusCode.NOT_FOUND.getCode())
                .message(StatusCode.NOT_FOUND.getMsg())
                .build());
        responseMessages.add(new ResponseMessageBuilder()
                .code(StatusCode.REPEATED_REQUEST.getCode())
                .message(StatusCode.REPEATED_REQUEST.getMsg())
                .build());
        responseMessages.add(new ResponseMessageBuilder()
                .code(StatusCode.INVALID_SIGNATURE.getCode())
                .message(StatusCode.INVALID_SIGNATURE.getMsg())
                .build());
        responseMessages.add(new ResponseMessageBuilder()
                .code(StatusCode.DB_OPERATION.getCode())
                .message(StatusCode.DB_OPERATION.getMsg())
                .build());
        responseMessages.add(new ResponseMessageBuilder()
                .code(StatusCode.ENCRYPT_FAILED.getCode())
                .message(StatusCode.ENCRYPT_FAILED.getMsg())
                .build());
        responseMessages.add(new ResponseMessageBuilder()
                .code(StatusCode.RATE_LIMIT.getCode())
                .message(StatusCode.RATE_LIMIT.getMsg())
                .build());

        return new Docket(DocumentationType.SWAGGER_2)
                .useDefaultResponseMessages(false)
                .globalResponseMessage(RequestMethod.GET, responseMessages)
                .globalResponseMessage(RequestMethod.PUT, responseMessages)
                .globalResponseMessage(RequestMethod.POST, responseMessages)
                .globalResponseMessage(RequestMethod.DELETE, responseMessages)
                .apiInfo(apiInfo(environment))
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                .paths(PathSelectors.any())
                .build()
                // .securitySchemes(securitySchemes())
                // .securityContexts(securityContexts())
                .globalOperationParameters(parameterList);
    }

    //    private List<SecurityScheme> securitySchemes() {
    //        return Collections.singletonList(new ApiKey(tokenHeaderName, tokenHeaderName, "header"));
    //    }
    //
    //    private List<SecurityContext> securityContexts() {
    //        List<SecurityContext> securityContexts = new ArrayList<>();
    //        securityContexts.add(SecurityContext.builder()
    //                .securityReferences(defaultAuth())
    //                .forPaths(PathSelectors.regex("^(?!auth).*$"))
    //                .build());
    //        return securityContexts;
    //    }

    //    private List<SecurityReference> defaultAuth() {
    //        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
    //        return Collections.singletonList(
    //                new SecurityReference(tokenHeaderName, new AuthorizationScope[] {authorizationScope}));
    //    }

    private ApiInfo apiInfo(Environment environment) {
        return new ApiInfoBuilder()
                .title(environment.getProperty("knife4j.openapi.title", "接口文档"))
                .description(environment.getProperty("knife4j.openapi.description", "服务端接口"))
                .version(environment.getProperty("knife4j.openapi.version", "V1.0.0"))
                .contact(new Contact(
                        environment.getProperty("knife4j.openapi.concat", "zebra"),
                        environment.getProperty("knife4j.openapi.email", "zhanghongbin@qq.com"),
                        environment.getProperty("knife4j.openapi.url", "https://gitee.com/zhanghongbin/zebra")))
                .build();
    }

    /**
     * 因为配置文件采用了
     * spring:
     * mvc:
     * throw-exception-if-no-handler-found: true
     * web:
     * resources:
     * add-mappings: false
     * 所以需要手动进行配置swagger
     *
     * @param registry 对象
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    /**
     * 解决 knife4j和Redisson问题，主要Redisson引入了spring-boot-starter-actuator造成的
     *
     * @return
     */
    @Bean
    public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
        return new BeanPostProcessor() {

            @Override
            @SuppressWarnings("all")
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof WebMvcRequestHandlerProvider) {
                    customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
                }
                return bean;
            }

            private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(
                    List<T> mappings) {
                mappings.removeIf(mapping -> mapping.getPatternParser() != null);
            }

            @SuppressWarnings("all")
            private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
                try {
                    Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                    field.setAccessible(true);
                    return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new IllegalStateException(e);
                }
            }
        };
    }
}
